package com.fluagen.example.vertx.templates;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import io.netty.util.internal.StringUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.TemplateHandler;
import io.vertx.ext.web.templ.HandlebarsTemplateEngine;

public class MainVerticle extends AbstractVerticle {

	public static final String CLI_HOST = "http.address";
	public static final String CLI_PORT = "http.port";
	public static final String CLI_OPEN_HOST = "open.host";
	public static final String CLI_OPEN_PORT = "open.port";
	public static final String CLI_OPEN_LOGIN_HOST = "login.open.host";
	public static final String CLI_OPEN_LOGIN_PORT = "login.open.port";

	private String host = "0.0.0.0";
//	private String host = "app.demo.com";
	private int port = 8083;
	private String openHost = "open.api.calis.edu.cn";
	private int openPort = 9130;
	private String loginHost = "login.open.api.calis.edu.cn";
	private int loginPort = 8081;

	HandlebarsTemplateEngine engine;

	WebClient client;

	public static Logger log = LoggerFactory.getLogger(MainVerticle.class);

	@Override
	public void start(Future<Void> future) throws Exception {
		client = WebClient.create(vertx);

		if (System.getProperty(CLI_HOST) != null) {
			host = System.getProperty(CLI_HOST);
		}
		if (System.getProperty(CLI_PORT) != null) {
			port = Integer.parseInt(System.getProperty(CLI_PORT));
		}
		if (System.getProperty(CLI_OPEN_HOST) != null) {
			openHost = System.getProperty(CLI_OPEN_HOST);
		}
		if (System.getProperty(CLI_OPEN_PORT) != null) {
			openPort = Integer.parseInt(System.getProperty(CLI_OPEN_PORT));
		}
		if (System.getProperty(CLI_OPEN_LOGIN_HOST) != null) {
			loginHost = System.getProperty(CLI_OPEN_LOGIN_HOST);
		}
		if (System.getProperty(CLI_OPEN_LOGIN_PORT) != null) {
			loginPort = Integer.parseInt(System.getProperty(CLI_OPEN_LOGIN_PORT));
		}

		Router router = Router.router(vertx);
		router.route("/*").handler(BodyHandler.create());
		router.route().handler(BodyHandler.create());
		engine = HandlebarsTemplateEngine.create();
		final TemplateHandler handler = TemplateHandler.create(engine);

		router.get("/").handler(this::login);
		router.get("/lsp").handler(this::lsp);
		router.post("/create_req_html").handler(this::createReqHtml);
		router.get("/create_req_param").handler(this::createReqParam);
		router.get("/callback.hbs").handler(this::appCallback);
		router.post("/token").handler(this::getAccessToken);
		router.post("/oplogin").handler(this::oplogin);
		router.get("/configure/service").handler(this::getConfigureService);
		router.post("/configure/service").handler(this::postConfigureService);

		router.post("/req/service").handler(this::postReqService);

		router.getWithRegex(".+\\.hbs").handler(handler);

		vertx.createHttpServer().requestHandler(router::accept).listen(port, host, result -> {
			if (result.succeeded()) {
				future.complete();
				log.info("Open API Client APP Demo start up [http://" + host + ":" + port + "]");

			} else {
				future.fail(result.cause());
			}
		});
	}

	private void login(RoutingContext context) {
		HttpServerResponse response = context.response();
		response.sendFile("public/index.html");
	}

	private void createReqParam(RoutingContext context) {
		engine.render(context, "templates", "/create_req_param", res -> {
			if (res.succeeded()) {
				context.response().end(res.result());
			} else {
				context.fail(res.cause());
			}
		});
	}

	private void createReqHtml(RoutingContext context) {
		HttpServerRequest request = context.request();
		String okapi_host = null;
		Integer okapi_port = null;
		String api = null;
		String method = null;
		String client_secret = null;
		JsonObject body = null;
		Map<String, String> headers = new HashMap<>();
		Map<String, String> params = new HashMap<>();
		Map<String, String> signParams = new HashMap<>();
		String temp = null;
		MultiMap entries = request.formAttributes();
		for (Map.Entry<String, String> e : entries) {
			String value = e.getValue();
			String key = e.getKey();
			if ("param_key".contentEquals(key)) {
				temp = value;
				params.put(value, null);
				continue;
			}
			if ("param_value".contentEquals(key) && temp != null) {
				params.put(temp, value);
				signParams.put(temp, value);
				temp = null;
				continue;
			}
			if ("header_key".contentEquals(key)) {
				temp = value;
				headers.put(value, null);
				continue;
			}
			if ("header_value".contentEquals(key) && temp != null) {
				headers.put(temp, value);
				temp = null;
				continue;
			}
			if ("okapi_host".contentEquals(key)) {
				okapi_host = value;
				continue;
			}
			if ("okapi_port".contentEquals(key)) {
				okapi_port = Integer.valueOf(value);
				continue;
			}
			if ("api".contentEquals(key)) {
				api = value;
				continue;
			}
			if ("method".contentEquals(key)) {
				method = value;
				continue;
			}
			if ("client_secret".contentEquals(key)) {
				client_secret = value;
				continue;
			}
			if ("body".contentEquals(key) && !StringUtil.isNullOrEmpty(value.trim())) {
				body = new JsonObject(value);
				continue;
			}
		}

		String sign = MD5Util.md5(signParams, client_secret);
		params.put("sign", sign);

		if (okapi_host != null) {
			context.put("okapi_host", okapi_host);
		}
		if (okapi_port != null) {
			context.put("okapi_port", okapi_port);
		}
		if (api != null) {
			context.put("api", api);
		}
		if (method != null) {
			context.put("method", method);
		}
		if (headers != null && headers.size() != 0) {
			context.put("headers", JsonObject.mapFrom(headers).toString());
		}
		if (params != null && params.size() != 0) {
			context.put("params", JsonObject.mapFrom(params).toString());
		}
		if (body != null) {
			context.put("body", body);
		}

		engine.render(context, "templates", "/req_param_html", res -> {
			if (res.succeeded()) {
				context.response().end(res.result());
			} else {
				context.fail(res.cause());
			}
		});

	}

	private void lsp(RoutingContext context) {
		HttpServerResponse response = context.response();
		// response.sendFile("public/lsp.html");

		context.put("redirect_uri", "http://" + host + ":" + port + "/callback.hbs");
		context.put("lp_uri", "http://" + loginHost + ":" + loginPort + "/loginp/code");

		engine.render(context, "templates", "/lsp", res -> {
			if (res.succeeded()) {
				context.response().end(res.result());
			} else {
				context.fail(res.cause());
			}
		});

	}

	private void oplogin(RoutingContext ctx) {
		HttpServerRequest request = ctx.request();

		String clientKey = request.getParam("client_id");
		String clientSecret = request.getParam("client_secret");
		String userType = request.getParam("user_type");
		String vf = request.getParam("view_format");
		String tenantId = request.getParam("tenant_id");
		String responseType = request.getParam("response_type");

		String cbURL = "http://" + host + ":" + port + "/callback.hbs";
		String opLoginURL = "http://" + loginHost + ":" + loginPort + "/libraries/oauth2/login";

		Map<String, String> signParams = new HashMap<String, String>();
		signParams.put("client_id", clientKey);
		signParams.put("tenant_id", tenantId);
		signParams.put("redirect_uri", cbURL);
		signParams.put("response_type", responseType);

		String sign = MD5Util.md5(signParams, clientSecret);
		StringBuilder redirectURL = new StringBuilder(opLoginURL);

		try {
			redirectURL.append("?");
			redirectURL.append("client_id").append("=").append(clientKey);
			redirectURL.append("&");
			redirectURL.append("tenant_id").append("=").append(tenantId);
			redirectURL.append("&");
			redirectURL.append("redirect_uri").append("=").append(URLEncoder.encode(cbURL, "UTF-8"));
			redirectURL.append("&");
			redirectURL.append("user_type").append("=").append(userType);
			redirectURL.append("&");
			redirectURL.append("view_format").append("=").append(vf);
			redirectURL.append("&");
			redirectURL.append("response_type").append("=").append(responseType);
			redirectURL.append("&");
			redirectURL.append("sign").append("=").append(sign);

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		ctx.put("opLoginURL", redirectURL.toString());
		log.info("opLoginURL:" + redirectURL.toString());

		engine.render(ctx, "templates", "/oplogin", res -> {
			if (res.succeeded()) {
				ctx.response().end(res.result());
			} else {
				ctx.fail(res.cause());
			}
		});
	}

	private void appCallback(RoutingContext context) {
		HttpServerRequest request = context.request();
		String code = request.getParam("code");

		context.put("code", code);

		context.next();
	}

	private void getAccessToken(RoutingContext context) {
		HttpServerRequest request = context.request();
		String code = request.getParam("code");
		String clientKey = request.getParam("client_id");
		String clientSecret = request.getParam("client_secret");
		String tenantId = request.getParam("tenant_id");
		String grantType = request.getParam("grant_type");
		String timestamp = System.currentTimeMillis() + "";

		Map<String, String> signParams = new HashMap<String, String>();
		signParams.put("client_id", clientKey);
		signParams.put("tenant_id", tenantId);
		signParams.put("timestamp", timestamp);
		signParams.put("code", code);
		signParams.put("sign_method", "md5");
		signParams.put("grant_type", grantType);

		String sign = MD5Util.md5(signParams, clientSecret);

		JsonObject body = new JsonObject();
		body.put("client_id", clientKey);
		body.put("tenant_id", tenantId);
		body.put("timestamp", timestamp);
		body.put("code", code);
		body.put("sign_method", "md5");
		body.put("sign", sign);
		body.put("grant_type", grantType);

		client.post(loginPort, loginHost, "/libraries/oauth2/get_access_token").putHeader("accept", "application/json")
				.putHeader("accept", "text/plain").putHeader("content-type", "application/json")
				.sendJsonObject(body, h -> {
					HttpResponse<Buffer> response = h.result();
					String rst = response.bodyAsString();
					JsonObject token = new JsonObject(rst);
					if (h.succeeded()) {
						// context.response().setStatusCode(200).putHeader("content-type",
						// "application/json").end(rst);
						context.put("token", token.getString("access_token"));
						context.put("client_id", clientKey);
						context.put("client_secret", clientSecret);
						context.put("tenant_id", tenantId);
						engine.render(context, "templates", "/token", res -> {
							if (res.succeeded()) {
								context.response().end(res.result());
							} else {
								context.fail(res.cause());
							}
						});

					} else {
						context.response().setStatusCode(500).end(h.cause().getMessage());
					}
				});

	}

	private void getConfigureService(RoutingContext context) {
		HttpServerRequest request = context.request();

		context.put("token", request.getParam("token"));
		context.put("openapi", "http://" + openHost + ":" + openPort);
		context.put("client_id", request.getParam("client_id"));
		context.put("client_secret", request.getParam("client_secret"));
		context.put("tenant", request.getParam("tenant_id"));

		engine.render(context, "templates", "/configure_service", res -> {
			if (res.succeeded()) {
				context.response().end(res.result());
			} else {
				context.fail(res.cause());
			}
		});

	}

	private void postConfigureService(RoutingContext context) {
		HttpServerRequest request = context.request();

		String basicCfg = request.getFormAttribute("svrInfo");
		String headerCfg = request.getFormAttribute("reqHeaders");
		String paramsCfg = request.getFormAttribute("reqParams");

		JsonObject svrInfo = new JsonObject(basicCfg);
		JsonObject reqHeaders = new JsonObject(headerCfg);
		JsonObject reqParams = new JsonObject(paramsCfg);

		String clientKey = svrInfo.getString("client_id");
		String clientSecret = svrInfo.getString("client_secret");
		String tenantId = svrInfo.getString("tenant_id");
		String timestamp = System.currentTimeMillis() + "";

		Map<String, String> signParams = new HashMap<String, String>();
		signParams.put("client_id", clientKey);
		signParams.put("tenant_id", tenantId);
		signParams.put("timestamp", timestamp);
		signParams.put("sign_method", "md5");
		reqParams.forEach(e -> {
			signParams.put(e.getKey(), (String) e.getValue());
		});

		String sign = MD5Util.md5(signParams, clientSecret);

		reqParams.put("client_id", clientKey);
		reqParams.put("tenant_id", tenantId);
		reqParams.put("timestamp", timestamp);
		reqParams.put("sign", sign);
		reqParams.put("sign_method", "md5");

		String openapi = svrInfo.getString("openapi");
		String serviceURI = svrInfo.getString("service_uri");
		
		if(!openapi.endsWith("/")){
			openapi += "/";
		}
		serviceURI = openapi + serviceURI;
				
		svrInfo.put("service_url", serviceURI);

		context.put("svrInfo", svrInfo);
		context.put("reqHeaders", reqHeaders);
		context.put("reqParams", reqParams);

		engine.render(context, "templates", "/request_service", res -> {
			if (res.succeeded()) {
				context.response().end(res.result());
			} else {
				context.fail(res.cause());
			}
		});

	}

	private void postReqService(RoutingContext context) {
		JsonObject body = context.getBodyAsJson();
		

		JsonObject svrInfo = body.getJsonObject("svrInfo");
		JsonObject reqHeaders = body.getJsonObject("reqHeaders");
		JsonObject reqParams = body.getJsonObject("reqParams");

		String methodName = svrInfo.getString("method_name");
		String serviceURL = svrInfo.getString("service_url");

		if (methodName.equalsIgnoreCase("get")) {

			HttpRequest<Buffer> req = client.getAbs(serviceURL);
			
			req.putHeader("Content-Type", "application/json; text/plain;");
			req.putHeader("Accept", "application/json; text/plain;");

			reqHeaders.forEach(e -> {
				req.putHeader(e.getKey(), (String) e.getValue());
			});
			reqParams.forEach(e -> {
				req.addQueryParam(e.getKey(), (String) e.getValue());
			});
			req.send(res -> {
				if (res.succeeded()) {
					String str = res.result().bodyAsString("UTF-8");
					log.info("request service ["+serviceURL+"] response:"+str);
					context.response().setStatusCode(res.result().statusCode()).end(str);
				} else {
					context.response().setStatusCode(500).end(res.cause().getMessage());
				}
			});
		} else {
			HttpRequest<Buffer> httpReq = client.postAbs(serviceURL);
			if (methodName.equalsIgnoreCase("put")) {
				httpReq = client.putAbs(serviceURL);
			} else if (methodName.equalsIgnoreCase("delete")) {
				httpReq = client.delete(serviceURL);
			}
			final HttpRequest<Buffer> req = httpReq;
			reqHeaders.forEach(e -> {
				req.putHeader(e.getKey(), (String) e.getValue());
			});
			req.sendJsonObject(reqParams, res -> {
				if (res.succeeded()) {
					context.response().setStatusCode(res.result().statusCode()).end(res.result().bodyAsString());
				} else {
					context.response().setStatusCode(500).end(res.cause().getMessage());
				}
			});
		}

	}

	public static void main(String[] args) {

		Runner.deploy(MainVerticle.class);
	}
}
